package han.chensing.latos.game.processor;

import han.chensing.latos.common.level.anim.LineAnimation;
import han.chensing.latos.common.level.timeLine.Frame;
import han.chensing.latos.common.level.timeLine.TimeLine;
import han.chensing.latos.common.level.timeLine.TimeLineEventSet;
import han.chensing.latos.common.level.timeLine.TimeLineRow;
import han.chensing.latos.common.level.timeLine.events.TimeLineEvent;
import han.chensing.latos.common.level.timeLine.events.TimeLineLineEvent;
import han.chensing.latos.common.level.timeLine.events.TimeLineLineMoveEvent;
import han.chensing.latos.game.elements.notes.Line;
import han.chensing.latos.game.elements.stage.LatosStage;
import han.chensing.latos.game.util.MathUtil;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

public class EventHandler {

    protected List<LineStatus> lineStatuses;
    protected LinkedList<TempLineEvent> selectedEvents;

    public EventHandler(){
        lineStatuses=new ArrayList<>();
        this.selectedEvents=new LinkedList<>();
    }

    public void select(float nowTime){
        for (LineStatus lineStatus : lineStatuses) {
            for (LinkedBlockingQueue<TempLineEvent> queue : lineStatus.getQueues()) {
                TempLineEvent peek = queue.peek();
                while (peek!=null&&!(peek.getTime() >= nowTime * 1000)){
                    queue.poll();
                    selectedEvents.add(peek);
                    peek= queue.peek();
                }
            }
        }
    }

    public void handle(LatosStage latosStage,float nowTime){
        Iterator<TempLineEvent> iterator = selectedEvents.iterator();
        while (iterator.hasNext()){
            TempLineEvent tempLineEvent = iterator.next();
            Object object = tempLineEvent.object;
            if (object instanceof TimeLineLineMoveEvent){
                if(handleLineAnimation(latosStage,tempLineEvent,nowTime))iterator.remove();
            }else if (object instanceof Frame){
                if(handleFrameChange(latosStage,tempLineEvent,nowTime))iterator.remove();
            }
        }
    }

    private boolean handleFrameChange(LatosStage latosStage,TempLineEvent tempLineEvent,float nowTimeS){
        Frame frame = (Frame) tempLineEvent.getObject();
        int nowTime= (int) (nowTimeS*1000);
        boolean reach = frame.getTime() < nowTime;
        Line bindLine = (Line) latosStage.getElements().get(tempLineEvent.getLineStatus().num);
        if (!reach)return false;
        switch (frame.getEventType()){
            case LineAnimation.EVENT_TYPE_X:{
                bindLine.setX(frame.getX());
                break;
            }
            case LineAnimation.EVENT_TYPE_Y:{
                bindLine.setY(frame.getY());
                break;
            }
            case LineAnimation.EVENT_TYPE_ROTATE:{
                bindLine.setRotation(frame.getRotate());
                break;
            }
            case LineAnimation.EVENT_TYPE_ALPHA:{
                bindLine.setAlpha(frame.getAlpha());
                break;
            }
            case LineAnimation.EVENT_TYPE_SPEED:{
                bindLine.getNoteSpeed().getSpeed().x= frame.getSpeed();
                break;
            }
            default:
        }
        return true;
    }

    private boolean handleLineAnimation(LatosStage latosStage,TempLineEvent tempLineEvent,float nowTimeS){
        TimeLineLineEvent timeLineEvent= (TimeLineLineEvent) tempLineEvent.object;
        Line bindLine = (Line) latosStage.getElements().get(tempLineEvent.getLineStatus().num);
        int nowTime= (int) (nowTimeS*1000);
        LineAnimation lineAnimation = timeLineEvent.getLineAnimation();
        int type = lineAnimation.getType();
        float endTime = timeLineEvent.getToKeyFrame().getTime() + lineAnimation.getDuration();
        float progress = (lineAnimation.getDuration()<=0)?1:1-(endTime - nowTime) / lineAnimation.getDuration();
        float delta = lineAnimation.getStartValue()-lineAnimation.getEndValue();
        float value = MathUtil.range(0,lineAnimation.getInterpolator().value(progress),1);
        switch (type){
            case LineAnimation.EVENT_TYPE_X:{
                bindLine.setX(lineAnimation.getStartValue()-delta*value);
                break;
            }
            case LineAnimation.EVENT_TYPE_Y:{
                bindLine.setY(lineAnimation.getStartValue()-delta*value);
                break;
            }
            case LineAnimation.EVENT_TYPE_ROTATE:{
                bindLine.setRotation(lineAnimation.getStartValue()-delta*value);
                break;
            }
            case LineAnimation.EVENT_TYPE_ALPHA:{
                bindLine.setAlpha(lineAnimation.getStartValue()-delta*value);
                break;
            }
            case LineAnimation.EVENT_TYPE_SPEED:{
                bindLine.getNoteSpeed().getSpeed().x=lineAnimation.getStartValue()-delta*value;
                break;
            }
            default:
        }
        return endTime <= nowTime;
    }

    public void resetTimeLine(TimeLine timeLine){
        this.timeLine=timeLine;
        for (TimeLineRow timeLineRow : timeLine.getTimeLineRows()) {
            int bindLineNum = timeLineRow.getBindLineNum();
            LineStatus lineStatus=new LineStatus();
            lineStatus.setNum(bindLineNum);
            for (TimeLineEventSet timeLineEventSet : timeLineRow.getTimeLineEventSets()) {
                ArrayList<TempLineEvent> objects=new ArrayList<>();
                for (Frame keyFrame : timeLineEventSet.getKeyFrames()) {
                    TempLineEvent tempLineEvent=new TempLineEvent();
                    tempLineEvent.object=keyFrame;
                    tempLineEvent.time=keyFrame.getTime();
                    tempLineEvent.lineStatus=lineStatus;
                    objects.add(tempLineEvent);
                }
                for (TimeLineEvent timeLineEvent : timeLineEventSet.getTimeLineEvents()) {
                    TempLineEvent tempLineEvent=new TempLineEvent();
                    tempLineEvent.object=timeLineEvent;
                    tempLineEvent.time=timeLineEvent.getFromKeyFrame().getTime();
                    tempLineEvent.lineStatus=lineStatus;
                    objects.add(tempLineEvent);
                }
                objects.sort((a,b)-> (int) (a.getTime()-b.getTime()));
                lineStatus.getQueues().add(new LinkedBlockingQueue<>(objects));
            }
            lineStatuses.add(lineStatus);
        }
    }

    public static class LineStatus{
        protected int num;
        protected LineAnimation lineAnimation;
        protected Frame frame;
        protected boolean needChange=false;
        protected List<LinkedBlockingQueue<TempLineEvent>> queues=new LinkedList<>();

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        public LineAnimation getLineAnimation() {
            return lineAnimation;
        }

        public void setLineAnimation(LineAnimation lineAnimation) {
            this.lineAnimation = lineAnimation;
        }

        public Frame getFrame() {
            return frame;
        }

        public void setFrame(Frame frame) {
            this.frame = frame;
        }

        public List<LinkedBlockingQueue<TempLineEvent>> getQueues() {
            return queues;
        }

        public boolean isNeedChange() {
            return needChange;
        }

        public void setNeedChange(boolean needChange) {
            this.needChange = needChange;
        }

    }
    public static class TempLineEvent{
        protected Object object;
        protected float time;
        protected LineStatus lineStatus;

        public Object getObject() {
            return object;
        }

        public void setObject(Object object) {
            this.object = object;
        }

        public float getTime() {
            return time;
        }

        public void setTime(float time) {
            this.time = time;
        }

        public LineStatus getLineStatus() {
            return lineStatus;
        }

        public void setLineStatus(LineStatus lineStatus) {
            this.lineStatus = lineStatus;
        }
    }
}
